-------------Thunder Bombs-------------
A 4am crack                  2018-02-04
---------------------------------------

Name: Thunder Bombs
Genre: arcade
Year: 1982
Credits: Tom Becklund
Publisher: Penguin Software
Platform: Apple ][+ or later
Media: single-sided 5.25-inch floppy
OS: custom

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  immediate disk read error, but it
  gets a participation certificate that
  spells out "You Tried"

Locksmith Fast Disk Backup
  unable to read any track

EDD 4 bit copy to .nib file on CFFA3000
  read errors on T12+
  copy fails to boot, hangs with the
  drive motor off

Copy ][+ nibble editor
  tracks $12-$22 appear unformatted
  Lower tracks have modified epilogues
    ("DA AA EB" instead of "DE AA EB")
  Odd tracks also have custom prologue
    ("D4 AA 96" instead of "D5 AA 96")
  no sign of anything beyond track $11
  no half or quarter tracks

Disk Fixer
  "O" for INPUT/OUTPUT CONTROL, set the
    epilogues to "DA AA EB"
  track 0 is readable
  boot sector is custom
  no sign of disk catalog on any track
  no sign of DOS or ProDOS or any OS
    whatsoever

Why didn't COPYA work?
  modified prologues/epilogues on
    every track

Why didn't Locksmith FDB work?
  ditto

Why didn't my .nib image work?
  This is a bit of a mystery. The
  alternating D4/D5 prologues shouldn't
  pose any problem for a nibble copier,
  so I'm assuming there is some sort of
  nibble check early in the boot.

Next steps:

  1. Run Passport to convert the disk
     to a standard format
  2. Patch the bootloader to read the
     newly standardized disk
  3. Find and disable the nibble check
  4. Declare victory (*)

                   ~

               Chapter 1
 In Which Our Automated Tools Take Us
     Just About As Far As They Can


The 2017-12-27 development version of
Passport successfully auto-converted
the disk to a standard format. The
built-in RWTS was designed to read this
kind of alternating D4/D5 prologue (it
was very common), and the adaptive RWTS
correctly determined the first epilogue
nibble (DA) and enforced it on the rest
of the sectors. All in all, I have high
confidence in the integrity of this
conversion.

More information and source code is
available at
https://archive.org/details/Passport4am

Here is the Passport transcript:

                 --v--

READING FROM S6,D1
USING BUILT-IN RWTS
T22 IS UNFORMATTED
WRITING TO RAM DISK
T21 IS UNFORMATTED
T20 IS UNFORMATTED
T1F IS UNFORMATTED
T1E IS UNFORMATTED
T1D IS UNFORMATTED
T1C IS UNFORMATTED
T1B IS UNFORMATTED
T1A IS UNFORMATTED
T19 IS UNFORMATTED
T18 IS UNFORMATTED
T17 IS UNFORMATTED
T16 IS UNFORMATTED
T15 IS UNFORMATTED
T14 IS UNFORMATTED
T13 IS UNFORMATTED
T12 IS UNFORMATTED
WRITING TO S5,D2

THE DISK WAS COPIED SUCCESSFULLY, BUT
PASSPORT DID NOT APPLY ANY PATCHES.

POSSIBLE REASONS:
- THE SOURCE DISK IS NOT COPY PROTECTED.
- THE TARGET DISK WORKS WITHOUT PATCHES.
- THE DISK USES AN UNKNOWN PROTECTION,
  AND PASSPORT CAN NOT HELP ANY FURTHER.

                 --^--

I did get to update my "is this track
unformatted" routine to detect the
kinda-but-not-strictly-unformatted
tracks $12-$22 on this disk. The new
algorithm declares that a track is
unformatted if it can't find 256 valid
disk nibbles in a row anywhere on the
track. For the purposes of discussion,
"valid disk nibble" means "one of the
nibbles used by the 6-and-2 encoding
algorithm" (see "Beneath Apple DOS"
figure 3-23). If you are reading this
far in the future, I may have refined
or replaced this algorithm again.

Onward.

The copy that Passport produces can not
read itself, which is not entirely
unexpected. It is most likely enforcing
the custom "DA AA EB" epilogue, which
is now the standard "DE AA EB". A quick
sector search for "BD 8C C0" (the
standard opcode to read the data latch
from a drive indexed by the X register)
found a DOS-shaped RWTS on track $0A,
which is interesting but not the code I
was looking for. (My failed copy never
gets off track 0.) So, putting a pin in
the fact that there is a secondary RWTS
that may be used later once the game is
loaded, I set off to trace the boot.

                   ~

               Chapter 2
         Boot Trace and Chill


[S6,D1=original disk]
[S5,D1=my work disk]

]PR#5
...
]CALL -151

*9600<C600.C6FFM

; copy boot sector to higher memory so
; it survives a reboot to my work disk
96F8-   A0 00       LDY   #$00
96FA-   B9 00 08    LDA   $0800,Y
96FD-   99 00 28    STA   $2800,Y
9700-   C8          INY
9701-   D0 F7       BNE   $96FA

; turn off slot 6 drive motor
9703-   AD E8 C0    LDA   $C0E8

; reboot to my work disk
9706-   4C 00 C5    JMP   $C500

*BSAVE TRACE,A$9600,L$109
*9600G
...reboots slot 6...
...reboots slot 5...

]BSAVE OBJ.0800-08FF,A$2800,L$100
]CALL -151

; move the boot sector back into place
*800<2800.28FFM
*801L

0801-   D8          CLD

; read ROM, write LC RAM bank 2
0802-   2C 81 C0    BIT   $C081

; check $FF58, a known location of an
; "RTS" opcode ($60) on all Apple II
; models
0805-   A9 60       LDA   #$60
0807-   4D 58 FF    EOR   $FF58

; if not found, hang forever (this is
; a defense against modified ROM chips
; that were popular in the Apple II
; underground)
080A-   D0 FE       BNE   $080A

; munge reset vector
080C-   8D F3 03    STA   $03F3

; disable interrupts
080F-   78          SEI

; re-use drive firmware at $Cx5C to
; read a few more sectors from track 0
0810-   AD 00 08    LDA   $0800
0813-   C9 06       CMP   #$06

; exit via $87A when all sectors have
; been read
0815-   B0 63       BCS   $087A
0817-   69 02       ADC   #$02
0819-   8D 00 08    STA   $0800
081C-   E6 3D       INC   $3D

; calculate re-entry point for calling
; the drive firmware, based on the boot
; slot
081E-   8A          TXA
081F-   4A          LSR
0820-   4A          LSR
0821-   4A          LSR
0822-   4A          LSR
0823-   09 C0       ORA   #$C0
0825-   8D 2A 08    STA   $082A

; call the drive firmware to read the
; next sector (exits via $0801, so this
; whole thing is a loop that only exits
; via the BCS at $0815)
0828-   4C 5C C6    JMP   $C65C

Execution continues at $087A once we've
read [...counts on fingers] three more
sectors into $0900..$0BFF.

*87AL

; turn off any peripheral ROM at $C800
087A-   2C FF CF    BIT   $CFFF

; wipe zero page
087D-   98          TYA
087E-   99 00 00    STA   $0000,Y
0881-   C8          INY
0882-   D0 FA       BNE   $087E

; save boot slot (x16) to a custom zero
; page location
0884-   86 30       STX   $30

; clear both hi-res screens
0886-   A2 3F       LDX   #$3F
0888-   99 00 20    STA   $2000,Y
088B-   C8          INY
088C-   D0 FA       BNE   $0888
088E-   EE 8A 08    INC   $088A
0891-   CA          DEX
0892-   10 F4       BPL   $0888

; reset stack
0894-   9A          TXS

; IN#0, PR#0, TEXT, &c.
0895-   20 89 FE    JSR   $FE89
0898-   20 93 FE    JSR   $FE93
089B-   20 84 FE    JSR   $FE84
089E-   20 51 FB    JSR   $FB51

; show hi-res screen 2
08A1-   2C 57 C0    BIT   $C057
08A4-   2C 52 C0    BIT   $C052
08A7-   2C 50 C0    BIT   $C050
08AA-   2C 55 C0    BIT   $C055

; read ROM, write LC RAM bank 2
08AD-   2C 81 C0    BIT   $C081
08B0-   2C 81 C0    BIT   $C081

; wipe page 3 vectors
08B3-   A9 D8       LDA   #$D8
08B5-   A8          TAY
08B6-   99 00 03    STA   $0300,Y
08B9-   C8          INY
08BA-   D0 FA       BNE   $08B6

; wipe F8 ROM space in language card
; (defense against software-based
; boot tracers like Inspector)
08BC-   B9 00 F8    LDA   $F800,Y
08BF-   99 00 F8    STA   $F800,Y
08C2-   88          DEY
08C3-   D0 F7       BNE   $08BC
08C5-   EE BE 08    INC   $08BE
08C8-   EE C1 08    INC   $08C1
08CB-   D0 EF       BNE   $08BC
08CD-   A9 02       LDA   #$02
08CF-   8D 10 01    STA   $0110
08D2-   8D 00 BD    STA   $BD00

; set lots of page 3 vectors (NMI, BRK,
; reset) and some low-level vectors too
; so everything points to $0110
; (defense against NMI capture cards
; like Wildcard that had a button you
; could press to break to the monitor
; and capture the game in memory)
08D5-   A0 10       LDY   #$10
08D7-   A9 01       LDA   #$01
08D9-   8C F0 03    STY   $03F0
08DC-   8D F1 03    STA   $03F1
08DF-   8C FE 03    STY   $03FE
08E2-   8D FF 03    STA   $03FF
08E5-   8C F2 03    STY   $03F2
08E8-   8D F3 03    STA   $03F3
08EB-   8C FA FF    STY   $FFFA
08EE-   8D FB FF    STA   $FFFB
08F1-   8C FC FF    STY   $FFFC
08F4-   8D FD FF    STA   $FFFD
08F7-   8C FE FF    STY   $FFFE
08FA-   8D FF FF    STA   $FFFF

; read LC RAM bank 2, no write
; (this remains active, so any attempt
; to hit Ctrl-Reset will be directed
; through the low-level reset vector
; at $FFFC, which we just set)
08FD-   2C 80 C0    BIT   $C080

And that's the end of the first boot
sector. Execution continues at $0900,
but I don't have that in memory yet.

Let's fix that.

*9600<C600.C6FFM

; set up a callback at $087A, after the
; boot sector reads additional sectors
; into $0900..$0BFF
96F8-   A9 4C       LDA   #$4C
96FA-   8D 7A 08    STA   $087A
96FD-   A9 0A       LDA   #$0A
96FF-   8D 7B 08    STA   $087B
9702-   A9 97       LDA   #$97
9704-   8D 7C 08    STA   $087C

; start the boot
9707-   4C 01 08    JMP   $0801

; callback is here --
; copy all the boot sectors to higher
; memory
970A-   A2 04       LDX   #$04
970C-   A0 00       LDY   #$00
970E-   B9 00 08    LDA   $0800,Y
9711-   99 00 28    STA   $2800,Y
9714-   C8          INY
9715-   D0 F7       BNE   $970E
9717-   EE 10 97    INC   $9710
971A-   EE 13 97    INC   $9713
971D-   CA          DEX
971E-   D0 EE       BNE   $970E

; turn off drive motor and reboot to
; my work disk
9720-   AD E8 C0    LDA   $C0E8
9723-   4C 00 C5    JMP   $C500

*BSAVE TRACE2,A$9600,L$126
*9600G
...reboots slot 6...
...reboots slot 5...

]BSAVE OBJ.0800-0BFF,A$2800,L$400
]CALL -151

; restore boot code to original address
*800<2800.2BFFM

Now we can continue with the listing,
right where we left off.

                   ~

               Chapter 3
        asdfjasklhfuiafgjklsfj


*900L

; finish setting every possible vector
; everywhere
0900-   49 A5       EOR   #$A5
0902-   8D F4 03    STA   $03F4
0905-   A9 4C       LDA   #$4C
0907-   8D EF 03    STA   $03EF
090A-   8D FD 03    STA   $03FD

; we literally just set the low-level
; reset vector (at $08F1), and now we
; are checking whether it's changed
090D-   CC FC FF    CPY   $FFFC

; no change, continue
0910-   F0 08       BEQ   $091A

; check if another known location is a
; known value ($E000, which is usually
; $4C, a JMP operation)
0912-   CD 00 E0    CMP   $E000

; yes, that checks out
0915-   F0 03       BEQ   $091A

; neither known value checked out,
; which probably means this machine
; only has 48K (no language card, so no
; upper 16K, so we've been storing
; values into thin air this whole time)
; so switch back to ROM and be happy
0917-   2C 81 C0    BIT   $C081

; execution continues here in any case
; (on machines with 64K, LC RAM bank 2
; is still active and the custom low-
; level reset vectors are in place) and
; OH GOD WHAT'S ALL THIS
091A-   A2 4C       LDX   #$4C
091C-   5D 2D 08    EOR   $082D,X
091F-   9D 10 01    STA   $0110,X
0922-   CA          DEX
0923-   10 F7       BPL   $091C

; OH GOD WHAT'S ALL THIS TOO
0925-   A0 03       LDY   #$03
0927-   AE 2B 08    LDX   $082B
092A-   5D 00 09    EOR   $0900,X
092D-   9D 00 05    STA   $0500,X
0930-   E8          INX
0931-   D0 F7       BNE   $092A
0933-   EE 2C 09    INC   $092C
0936-   EE 2F 09    INC   $092F
0939-   88          DEY
093A-   D0 EE       BNE   $092A

the rest of the bootloader is encrypted
asdfjasklhfuiafgjklsfj that's great drm
is great grrrrrrrrrrrrrrrrrrrrrrrr okay
here we go

; set some stuff in the code we just
decrypted onto the stack
093C-   A5 30       LDA   $30
093E-   09 89       ORA   #$89
0940-   8D 11 01    STA   $0111
0943-   AD 2A 08    LDA   $082A
0946-   8D 58 01    STA   $0158

; and continue inside the decrypted
code on the text page
0949-   4C 76 07    JMP   $0776

I can't ignore it any longer. It's time
to decrypt the bootloader.

; restore the original boot sector (the
; one in memory has my trace callback
; in it)
*BLOAD OBJ.0800-08FF,A$800

I can't modify the decryption loop,
because it forms the basis for the key
to decrypt later pages.

So that's great.

But I can move the decryption loop and
modify the copy.

; copy code to $2900
*2900<900.9FFM

; fix up the absolute addresses so we
; decrypt into $2100 instead of $0100
*2921:21

; $2500 instead of $0500
*292F:25

; and self-modify our loop at $2900
; instead of $0900
*2935:29
*2938:29

; the initial key is in the accumulator
; (set at $0905 before some other stuff
; I don't want to run) so I'll manually
; set it here at the beginning of the
; decryption -- it's $4C
*2917:EA A9 4C

; fix up the post-decryption patchups
*293C:A9 60
*2942:21
*2948:21

; "RTS" instead of "JMP $0776"
*2949:60

The final patched decryption code looks
like this:

*2917L

2917-   EA          NOP
2918-   A9 4C       LDA   #$4C
291A-   A2 4C       LDX   #$4C
291C-   5D 2D 08    EOR   $082D,X
291F-   9D 10 21    STA   $2110,X
2922-   CA          DEX
2923-   10 F7       BPL   $291C
2925-   A0 03       LDY   #$03
2927-   AE 2B 08    LDX   $082B
292A-   5D 00 09    EOR   $0900,X
292D-   9D 00 25    STA   $2500,X
2930-   E8          INX
2931-   D0 F7       BNE   $292A
2933-   EE 2C 29    INC   $292C
2936-   EE 2F 29    INC   $292F
2939-   88          DEY
293A-   D0 EE       BNE   $292A
293C-   A9 60       LDA   #$60
293E-   09 89       ORA   #$89
2940-   8D 11 21    STA   $2111
2943-   AD 2A 08    LDA   $082A
2946-   8D 58 21    STA   $2158
2949-   60          RTS

Let's go!

*2917G

*2110L

; looks like a Badlands routine ends
; up at $0110 -- this is what all the
; reset and other vectors were pointing
; to
2110-   AC E9 C0    LDY   $C0E9

; clear text screen
2113-   A9 28       LDA   #$28
2115-   85 21       STA   $21
2117-   A9 18       LDA   #$18
2119-   85 23       STA   $23
211B-   A9 00       LDA   #$00
211D-   85 20       STA   $20
211F-   85 22       STA   $22
2121-   85 24       STA   $24
2123-   85 25       STA   $25
2125-   20 58 FC    JSR   $FC58

; show it (now blank)
2128-   AE 54 C0    LDX   $C054

; wipe every other byte of memory (by
; which I mean every alternating byte,
; because we increment the Y index
; twice, which is a little weird but
; whatever, if we end up here we've
; already lost)
212B-   A0 00       LDY   #$00
212D-   84 4E       STY   $4E
212F-   A9 02       LDA   #$02
2131-   A2 08       LDX   #$08
2133-   86 4F       STX   $4F
2135-   91 4E       STA   ($4E),Y
2137-   C8          INY
2138-   C8          INY
2139-   D0 FA       BNE   $2135
213B-   E8          INX

; skip hi-res screen 1 for some reason
213C-   E0 20       CPX   #$20
213E-   F0 19       BEQ   $2159
2140-   E0 C0       CPX   #$C0
2142-   90 EF       BCC   $2133
2144-   EC 81 C0    CPX   $C081
2147-   AC 81 C0    LDY   $C081

; wipe wipe wipe your RAM,
; gently down the board
214A-   9D 00 03    STA   $0300,X
214D-   BD 00 FF    LDA   $FF00,X
2150-   9D 00 FF    STA   $FF00,X
2153-   E8          INX
2154-   D0 F4       BNE   $214A

; reboot from whence we came (this JMP
; address was actually modified by the
; post-decryption patchups at $093C,
; to maintain slot independence even in
; the face of catastrophic failure)
2156-   4C 00 C6    JMP   $C600
2159-   A2 40       LDX   #$40
215B-   D0 D6       BNE   $2133

OK, let's not end up there.

*2776L

2776-   A9 02       LDA   #$02
2778-   85 0A       STA   $0A
277A-   85 02       STA   $02
277C-   A9 BE       LDA   #$BE
277E-   85 05       STA   $05
2780-   20 0A 07    JSR   $070A
2783-   AD 2A 06    LDA   $062A
2786-   48          PHA
2787-   AD 29 06    LDA   $0629
278A-   48          PHA
278B-   20 AC 06    JSR   $06AC

I don't know what that does yet, but it
sure does look like real code, so I
think the decryption was a success.

                   ~

               Chapter 4
     Oh Look You Made A Save Icon


Let's save what we have so far, since
someone went to all this trouble to
prevent us from seeing it.

*BSAVE OBJ.0100-01FF,A$2100,L$100
*BSAVE OBJ.0500-07FF,A$2500,L$300

What I really want is a disk with this
decrypted code on it, paired with a
bootloader that no longer tries to
decrypt it. Then I can start making the
rest of the changes to make it boot.

I have everything in memory -- the
unencrypted parts and the decrypted
parts -- but not all in one place. So
the next step is to combine them.

; copy decrypted code back to page 8
*82D<2110.215CM

; copy decrypted code back to page 9+
*94C<254C.27FFM

; change "EOR" instructions to "LDA" --
; abracadabra! the decryption loops
; become copy loops
*91C:BD
*92A:BD

*BSAVE OBJ.0800-0BFF DECRYPTED,
 A$800,L$400

Now let's write that decrypted version
back to my copy.

; loop through all sectors on track 0,
; writing the ones we want and skipping
; the rest
28C0-   AC ED 28    LDY   $28ED
28C3-   B9 D8 28    LDA   $28D8,Y
28C6-   30 0A       BMI   $28D2
28C8-   8D F1 28    STA   $28F1
28CB-   A9 28       LDA   #$28
28CD-   A0 E8       LDY   #$E8
28CF-   20 D9 03    JSR   $03D9
28D2-   CE ED 28    DEC   $28ED
28D5-   10 E9       BPL   $28C0
28D7-   60          RTS

*28D8.28E7

; T00,S00 <- $0800
; T00,S0E <- $0900
; T00,S0D <- $0A00
; T00,S0C <- $0B00
28D8- 08 FF FF FF FF FF FF FF
28E0- FF FF FF FF 0B 0A 09 FF

*28E8.28FF

; standard RWTS parameter table
28E8- 01 60 01 00 00 0F FB 28
28F0- 00 00 00 00 02 00 FE 60
28F8- 01 00 00 00 01 EF D8 00

*BSAVE WRITE,A$28C0,L$40

[S6,D1=non-working copy]

*28C0G
...write write write...

Now we have a copy that is just as non-
functional as my previous copy, but at
least it's no longer encrypted.

                   ~

               Chapter 5
           Are We There Yet?


Now I get to normalize the newly
decrypted bootloader so my copy can
read itself. The code to match and
parse the address field starts at $054C
(in memory at $254C):

*254CL

; boot slot (x16)
254C-   A6 30       LDX   $30

254E-   BD 8C C0    LDA   $C08C,X
2551-   10 FB       BPL   $254E

; side effect -- stores $D4 or $D5 in
; zero page, might be relevant later
2553-   85 01       STA   $01
2555-   EA          NOP
2556-   EA          NOP

; match $D4 or $D5 (should be fine,
; since it will still match $D5 on my
; copy), then $AA $96 as normal
2557-   4A          LSR
2558-   49 6A       EOR   #$6A
255A-   D0 F2       BNE   $254E
255C-   BD 8C C0    LDA   $C08C,X
255F-   10 FB       BPL   $255C
2561-   C9 AA       CMP   #$AA
2563-   D0 E9       BNE   $254E
2565-   EA          NOP
2566-   BD 8C C0    LDA   $C08C,X
2569-   10 FB       BPL   $2566
256B-   49 96       EOR   #$96
256D-   D0 DF       BNE   $254E

; parse address field and put in zero
; page $2C..$2F as normal
256F-   A0 03       LDY   #$03
2571-   85 0C       STA   $0C
2573-   BD 8C C0    LDA   $C08C,X
2576-   10 FB       BPL   $2573
2578-   2A          ROL
2579-   85 0B       STA   $0B
257B-   BD 8C C0    LDA   $C08C,X
257E-   10 FB       BPL   $257B
2580-   25 0B       AND   $0B
2582-   99 2C 00    STA   $002C,Y
2585-   45 0C       EOR   $0C
2587-   88          DEY
2588-   10 E7       BPL   $2571
258A-   A8          TAY
258B-   D0 0B       BNE   $2598

; match address epilogue (one nibble)
258D-   BD 8C C0    LDA   $C08C,X
2590-   10 FB       BPL   $258D
2592-   C9 DA       CMP   #$DA    <-- !
2594-   D0 02       BNE   $2598
2596-   18          CLC
2597-   60          RTS
2598-   38          SEC
2599-   60          RTS

Now that the disk uses the standard
epilogue ($DE $AA $EB), we'll change
$DA to $DE.

This code is on sector $0E.

T00,S0E,$93: DA -> DE

Then comes the data field parsing:

259A-   A6 30       LDX   $30
259C-   A0 18       LDY   #$18
259E-   88          DEY
259F-   30 F7       BMI   $2598

; match $D5 $AA $AD as normal
25A1-   BD 8C C0    LDA   $C08C,X
25A4-   10 FB       BPL   $25A1
25A6-   C9 D5       CMP   #$D5
25A8-   D0 F4       BNE   $259E
25AA-   EA          NOP
25AB-   BD 8C C0    LDA   $C08C,X
25AE-   10 FB       BPL   $25AB
25B0-   C9 AA       CMP   #$AA
25B2-   D0 F2       BNE   $25A6
25B4-   A0 56       LDY   #$56
25B6-   BD 8C C0    LDA   $C08C,X
25B9-   10 FB       BPL   $25B6
25BB-   49 AD       EOR   #$AD
25BD-   D0 E7       BNE   $25A6

; ???
25BF-   08          PHP
25C0-   20 96 05    JSR   $0596
25C3-   28          PLP

*2596L

2596-   18          CLC
2597-   60          RTS

Nothing. We're calling a subroutine to
do absolutely nothing except clear the
carry. And since we saved and restored
the status flags, we're not even doing
that.

Onward.

*25C4L

25C4-   88          DEY
25C5-   84 0B       STY   $0B
25C7-   BC 8C C0    LDY   $C08C,X
25CA-   10 FB       BPL   $25C7
25CC-   59 D6 02    EOR   $02D6,Y
25CF-   A4 0B       LDY   $0B
25D1-   99 00 03    STA   $0300,Y
25D4-   D0 EE       BNE   $25C4
25D6-   84 0B       STY   $0B
25D8-   BC 8C C0    LDY   $C08C,X
25DB-   10 FB       BPL   $25D8
25DD-   59 D6 02    EOR   $02D6,Y
25E0-   A4 0B       LDY   $0B
25E2-   99 00 02    STA   $0200,Y
25E5-   C8          INY
25E6-   D0 EE       BNE   $25D6
25E8-   BC 8C C0    LDY   $C08C,X
25EB-   10 FB       BPL   $25E8
25ED-   59 D6 02    EOR   $02D6,Y
25F0-   D0 A6       BNE   $2598

; also extremely weird
25F2-   A1 00       LDA   ($00,X)

; match all three epilogue nibbles
25F4-   BD 8C C0    LDA   $C08C,X
25F7-   10 FB       BPL   $25F4
25F9-   C9 DA       CMP   #$DA    <-- !
25FB-   D0 9B       BNE   $2598
25FD-   EA          NOP
25FE-   BD 8C C0    LDA   $C08C,X
2601-   10 FB       BPL   $25FE
2603-   C9 AA       CMP   #$AA
2605-   D0 91       BNE   $2598
2607-   A4 2F       LDY   $2F
2609-   BD 8C C0    LDA   $C08C,X
260C-   10 FB       BPL   $2609
260E-   C9 EB       CMP   #$EB
2610-   D0 86       BNE   $2598

; finish decoding disk nibbles into
; bytes in memory
2612-   A2 56       LDX   #$56
2614-   CA          DEX
2615-   30 FB       BMI   $2612
2617-   B9 00 02    LDA   $0200,Y
261A-   5E 00 03    LSR   $0300,X
261D-   2A          ROL
261E-   5E 00 03    LSR   $0300,X
2621-   2A          ROL
2622-   91 04       STA   ($04),Y
2624-   C8          INY
2625-   D0 ED       BNE   $2614
2627-   18          CLC
2628-   60          RTS

OK, the most obvious patch is the data
epilogue, which is now $DE $AA $EB.

T00,S0E,$FA: DA -> DE

Unfortunately, my copy still grinds on
boot. Which means I'm still missing
something.

It's not the address prologue matching;
the LSR/EOR code to match $D4 or $D5
is a common trick. It still matches $D5
on any track, so it shouldn't require
any change.

After staring at this code for longer
than I would like to admit, it finally
dawned on me what this weird code
immediately after the data prologue is
for:

; cycles counts in margin, because that
; is the most important part
25B6-   BD 8C C0    LDA   $C08C,X   ; 4
25B9-   10 FB       BPL   $25B6     ; 2
25BB-   49 AD       EOR   #$AD      ; 2
25BD-   D0 E7       BNE   $25A6     ; 2
25BF-   08          PHP             ; 3
25C0-   20 96 05    JSR   $0596     ; 6

2596-   18          CLC             ; 2
2597-   60          RTS             ; 6

25C3-   28          PLP             ; 4
25C4-   88          DEY             ; 2
25C5-   84 0B       STY   $0B       ; 3
25C7-   BC 8C C0    LDY   $C08C,X   ;*3
25CA-   10 FB       BPL   $25C7

4+2+2+2+3+6+2+6+4+2+3+3 = 39, which is
too long. Each bit on disk takes 4 CPU
cycles to come around as the disk is
spinning.  That means we need to read
an 8-bit nibble every 32 cycles. The
data latch will hold the last value for
4 more cycles -- to compensate for the
fact that the read is actually done on
the third cycle of the 4-cycle LDA/LDY
instruction that hits the data latch
soft switch -- so we can spend an
absolute maximum of 36 cycles fetching
any one nibble. We've intentionally
wasted enough time that we'll miss the
first bit of the first nibble of the
data field.

Unless...

If the $AD, the third data prologue
nibble, has a timing bit after it, then
the data latch would hold that value
for 4 more cycles, and we would just
barely have enough time to catch the
first bit of the next nibble.

Turning back to the Copy II Plus nibble
editor, I can see it on the original
disk (originally shown in inverse,
which I converted to "+"):

                 --v--

   COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
---------------------------------------

TRACK: 01  START: 2FA8  LENGTH: 186A

2F88: E9 EB 97 BD 95 B5 9C DF   VIEW
2F90: B4 97 94 D4 94 BD 9F D6
2F98: DC FE F4 95 DD 97 F4 97
2FA0: 94 BD 9D D5 FE+FF+FF+FF+
2FA8: D4 AA 96 AA AA AA AB AA
      ^^^^^^^^
  address prologue

2FB0: AA AA AB DA AA EB 99 FF+
               ^^^^^^^^
            address epilogue

2FB8: EB E9+FF+F9 FE+FF+FF+D5
                           ^^
2FC0: AA AD+EC EC D7 D9 AB 9A
      ^^^^^
 data prologue
with timing bit
after the third
    nibble

2FC8: E6 D9 96 9A DE FD B5 9A

                 --^--

At $05BF, I can put a "BEQ" to branch
over the JSR, which will save enough
time to make this code work on my
normalized disk with no extra timing
bits.

T00,S0E,$BF: 0820 -> F003

]PR#6
...still grinds...

I. Am. Still. Missing. Something.

                   ~

               Chapter 6
          And One More Thing


After staring at this bootloader for an
embarrassing amount of time, looking at
the original disk in both a nibble and
a sector editor, I found another
difference: the disk volume number.

The original disk has a disk volume
number of 0 -- which, by the way, is
impossible to create with standard
tools. But as we've already seen, this
disk is anything but standard.

(My copy has a disk volume 254, the
default.)

But who cares? On an unprotected disk,
or even a protected disk with a
modified copy of DOS 3.3, the RWTS can
check and intentionally reject a disk
with the wrong disk volume number. This
bootloader does not contain that check.

Except...

At $0607, the custom RWTS is finishing
up matching the data field epilogue and
getting ready to finish decoding the
disk nibbles in the data field and
verifying the checksum before returning
a final boolean in the carry flag --
yes, this sector was read successfully,
or no, it wasn't.

``'-.,_,.-'``'-.,_,.='``'-.,_,.-'``'-.,
``'-.,_,.-'``'-.,_,.='``'-.,_,.-'``'-.,
``                                   .,
`` 2607-   A4 2F       LDY   $2F     .,
`` 2609-   BD 8C C0    LDA   $C08C,X .,
`` 260C-   10 FB       BPL   $2609   .,
`` 260E-   C9 EB       CMP   #$EB    .,
`` 2610-   D0 86       BNE   $2598   .,
``                                   .,
``'-.,_,.-'``'-.,_,.='``'-.,_,.-'``'-.,
``'-.,_,.-'``'-.,_,.='``'-.,_,.-'``'-.,

Look how we're initializing the Y
register: with zero page $2F. What's in
$2F? Why, we just set that, while we
were parsing the address field. It's
the disk volume number.

The disk volume number has nothing to
do with this part of the RWTS. The Y
register is used to look up into the
nibble translation table. In a standard
DOS 3.3 RWTS, that register is always
initialized to 0. So this is a very,
very, very sneaky way of ensuring that
the disk volume number is 0.

Which, of course, mine isn't.

The patch is to make the code do what
DOS 3.3 does: always initialize the Y
register to 0 going into the final
nibble decoding.

T00,S0D,$07: A42F -> A000

]PR#6
...works...

Whew.

Except...

                   ~

               Chapter 7
       And One More Thing, Again


The game boots. The game loads. The
game plays. Then you get a high score,
you enter your initials, and... the
game reboots.

Because there is AN ENTIRELY OTHER RWTS
on the disk for the exclusive purpose
of writing out high scores. It's stored
in reverse order on track $0A, but it
looks like it gets loaded into $B800 in
memory. (I actually found this RWTS by
accident when I was looking for the
bootloader. Talk about 4shadowing!)

T0A,S0A,$35: DA -> DE
T0A,S0A,$91: DA -> DE
T0A,S0B,$9E: DA -> DE

]PR#6
...works, and it is glorious...

Quod erat liberandum.

                   ~

            Acknowledgments


Thanks to Ian Baronofsky for lending me
the original disk.

---------------------------------------
A 4am crack                    No. 1676
------------------EOF------------------
